08 فصل هشتم - تفکر پایتونی

فصل ۸

رشته ها

#### ۸.۱ رشته یعنی دنباله

هر رشته یک دنباله از کاراکتر هاست. شما میتوانید هر بار به یکی از کاراکتر ها با استفاده از عملگر براکت دسترسی پیدا کنید.

>>> fruit = ‘banana’

>>> letter = fruit[1]

دستور دوم کاراکتر شماره ۱ را از متغیر fruit انتخاب کرده و در متغیر letter قرار میدهد.

عبارت داخل براکت اندیس نامیده میشود. اندیس نشان میدهد که شما کدام کاراکتر داخل دنباله را میخواهید.

البته ممکن است نتیجه مطابق میل شما نباشد:

>>> print letter

a

برای بیشتر افراد، اولین کاراکتر در 'banana' کاراکتر b است، نه a. ولی برای متخصصان کامپیوتر، اندیس فاصله هر کاراکتر از ابتدای رشته است، و فاصله اولین کاراکتر از ابتدای رشته صفر است.

>>> letter = fruit[0]

>>> print letter

b

پس b صفرمین کاراکتر در رشته 'banana' است، a اولین کاراکتر و n دومین کاراکتر است.

شما میتوانید از هر عبارتی داخل براکت به عنوان اندیس استفاده کنید، از جمله متغیرها و عملگرها، ولی اندیس همیشه باید عدد صحیح باشد. در غیر اینصورت با این خطا مواجه میشوید.

>>> letter = fruit[1.5]

TypeError: string indices must be integers

#### ۸.۲ len

len یک تابع داخلی است که تعداد کاراکتر های داخل یک رشته را بر میگرداند.

>>> fruit = ‘banana’

>>> len(fruit)

6

شاید بخواهید برای گرفتن آخرین کاراکتر یک رشته به این شکل عمل کنید:

>>> length = len(fruit)

>>> last = fruit[length]

IndexError: string index out of range

دلیل IndexError این است که هیچ کاراکتری در 'banana' با اندیس ۶ وجود ندارد. از آنجا که ما برای شمردن از صفر شروع میکنیم، ۶ کاراکتر دارای اندیس هایی بین صفر تا ۵ هستند.

>>> last = fruit[length-1]

>>> print last

a

همچنین میتوانید از اندیس های منفی استفاده کنید، که به این شکل برعکس از انتهای رشته شمرده میشود. به این شکل که عبارت [fruit[-1 نشان دهنده آخرین کاراکتر، [fruit[-2 نشان دهنده کاراکتر یکی مانده به آخر است.

#### ۸.۳ گردش با استفاده از حلقه for

بیشتر محاسبات شامل پردازش یک کاراکتر در هر لحظه است. معمولا در این محاسبات از ابتدای رشته شروع میکنند، هر کاراکتر را در هر مرتبه انتخاب میکنند و پس از انجام یک سری عملیات، به کاراکتر بعدی میروند. به این شیوه پردازش گردش میگویند. یک راه نوشتن یک گردش، استفاده از حلقه while است:

index = 0

while index < len(fruit):

letter = fruit[index]

print letter

index = index + 1

این حلقه در رشته میچرخد و هر کاراکتر را در یک خط جداگانه نمایش میدهد. شرط حلقه

(index < len(fruit است، پس وقتی index برابر با طول رشته شود، شرط دیگر برقرار نیست و باعث میشود که بدنه حلقه اجرا نشود. آخرین کاراکتری که به عنوان دسترسی پیدا میشود دارای اندیس len(fruit) - 1 است، که آخرین کاراکتر رشته است.

تمرین ۸.۱ تابعی بنویسید که یک رشته را به عنوان آرگومان دریافت کند و از انتها، شروع به نمایش کاراکتر ها در هر خط کند.

یک راه دیگر نوشتن گردش استفاده از حلقه for است:

for char in fruit:

print char

هر بار در حلقه، کاراکتر جدید به متغیر char نسبت داده میشود. حلقه تا جایی که کاراکتری باقی نماند ادامه پیدا میکند.

مثال بعد نشان می دهد که چطور میتوان از عملگر جمع برای الحاق دو رشته با استفاده از حلقه for کلمات مختلف ایجاد کرد.

prefixes = ‘JKLMNOPQ’

suffix = ‘ack’

for letter in prefixes:

print letter + suffix

خروجی:

Jack

Kack

Lack

Mack

Nack

Oack

Pack

Qack

تمرین ۸.۲ مثال رو طوری اصلاح کنید که به جای 'Oack' و 'Qack' کلمه های 'Ouack' و 'Quack' نمایش داده شود.

#### ۸.۴ تکه های رشته

هر قطعه از یک رشته تکه نام دارد. انتخاب یک تکه مانند انتخاب یک کاراکتر است:

>>> s = ‘Monty Python’

>>> print s[0:5]

Monty

>>> print s[6:13]

Python

عملگر [n:m] مقدار تکه ای از رشته از nمین کاراکتر تا mمین کاراکتر را برمیگرداند که شامل کاراکتر شماره n میشود ولی شامل کاراکتر شماره m نمیشود. این رفتار شاید غیرمنطقی به نظر برسد ولی ممکن است این تصویر به شما کمک کند.

fruit->’banana’

اگر عدد قبل از 'دو نقطه' رو پاک کنید ( [5:] ) باعث میشود که تکه از اولین کاراکتر شروع شود و اگر عدد بعد از دو نقطه را پاک کنید ( [:6] ) تکه تا آخرین کاراکتر ادامه پیدا میکند.

>>> fruit = ‘banana’

>>> fruit[:3]

ban

>>> fruit[3:]

ana

اگر اندیس اول برابر با اندیس دوم باشد باعث میشود یک رشته خالی برگردانده شود، که به این شکل نمایش داده میشود.

>>> fruit = ‘banana’

>>> fruit[3:3]

‘’

رشته خالی هیچ کاراکتری ندارد و دارای طول صفر است. به جز این تمام خصوصیات یک رشته خالی مانند یک رشته معمولی است.

تمرین ۸.۳ با توجه به اینکه متغیر fruit یک رشته است،[:]fruit چه معنی ای دارد؟

#### ۸.۵ رشته ها تغییر ناپذیرند

ممکن است بخواهید از عملگر [ ] در سمت چپ یک تساوی استفاده کنید تا یک تکه از رشته را تغییر دهید. به طور مثال:

>>> greeting = ‘Hello, world!’

>>> greeting[0] = ‘J’

TypeError: object does not support item assignment

در اینجا شئ همان رشته و آیتم هم کاراکتری است که سعی کردیم تغییر دهیم. در حال حاضر هر شئ را همان 'مقدار' تصور کنید، ولی بعدا این مفهوم را تعریف میکنیم. یک آیتم یک کاراکتر در یک دنباله است.

دلیل نمایش این خطا این است که رشته ها تغییر ناپذیرند، به این معنی که شما نمیتوانید یک رشته تعریف شده را تغییر دهید. بهترین کاری که میتوان کرد این است که یک رشته دیگر تعریف کنید که شامل بخشی از رشته اصلی است:

>>> greeting = ‘Hello, world!’

>>> new_greeting = ‘J’ + greeting[1:]

>>> print new_greeting

Jello, world!

با اینکار یک کاراکتر به تکه ای از greeting اضافه خواهد کرد و هیچ تغییری روی متغیر greeting ایجاد نمیکند.

#### ۸.۶ جستجو

تابع زیر چه کاری انجام میدهد؟

def find(word, letter):\ index = 0\ while index < len(word):\ if word[index] == letter:\ return index\ index = index + 1\ return -1

شاید بتوان گفت که تابع find متضاد عملگر [ ] می‌باشد. به جای گرفتن اندیس و دریافت کاراکتر متناسب، این تابع کاراکتر را به عنوان ورودی می‌پذیرد و اندیس آن کاراکتر را به عنوان خروجی می‌دهد. اگر کاراکتر پیدا نشد تابع مقدار -۱ را برمیگرداند.

این اولین باریست که از return استفاده میکنیم. اگر word[index] == letter، تابع بلافاصله از حلقه خارج میشود و خروجی را برمیگرداند.

اگر کاراکتر در رشته وجود نداشته باشد، برنامه به شکل قابل پیش بینی از حلقه خارجی میشود و مقدار -۱ را برمیگرداند.

این پترن محاسبه، گردش در یک رشته و برگرداندن هنگامی که چیزی که به دنبالش هستیم پیدا شد، جستجو نامیده میشود.

تمرین ۴

تابع find را ویرایش کنید که پارامتر سومی هم بگیرد، اندیسی در رشته که جستجو از آنجا شروع شود.

۸.۷ حلقه و شمارش

برنامه زیر تعداد دفعاتی که حرف a در یک رشته دیده میشود را می‌شمارد:

word = 'banana'\ count = 0\ for letter in word:\ if letter == 'a':\ count = count + 1\ print count

این برنامه پترن دیگری از محاسبه به نام شمارشگر (Counter) را معرفی میکند. متغیر count با مقدار ۰ ایجاد میشود و هربار که a پیدا شد یکی به آن اضافه میشود. وقتی حلقه تمام میشود، count مقدار نهایی را در خود دارد، تعداد aها.

تمرین ۵

این کد را در یک تابع قرار دهید (Encapsulate) و آن را عمومی سازی کنید، یعنی به a هر رشته دیگری بتوان به عنوان پارامتر به آن داد.

تمرین ۶

این تابع را طوری ویرایش کنید که به جای گردش در رشته، از تابع سه پارامتری find در موضوع قبل استفاده کند.

۸.۸ متد های رشته

یک متد مشابه تابع است، آرگومان هایی دریافت میکند و مقداری را بازمیگرداند، ولی Syntax متفاوت است. به طور مثال، متد upper یک رشته میگیرد و یک رشته تازه با حروف بزرگ بازمیگرداند:

به جای Syntax تابع مانند به شکل upper(word) از یک Syntax متد به شکل word.upper() استفاده میشود.

>>> word = ‘banana’

>>> new_word = word.upper()

>>> print new_word

BANANA

این شیوه استفاده از نماد نقطه نشان میدهد که نام متد، upper، و نام رشته‌ای که متد روی آن اعمال میشود word هست.

اجرای متد فراخوانی (Invocation) نام دارد؛ در این حالت میگوییم که متد upper روی word فراخوانی شده است.

آن طور که مشخص می‌شود، یک متد مخصوص رشته به اسم find مشابه تابعی که ما نوشتیم وجود دارد:

>>> word = ‘banana’

>>> index = word.find(‘a’)

>>> print index

1

در این مثال، ما متد find را روی word فراخوانی میکنیم و حرفی که میخواهیم در رشته پیدا کنیم را به عنوان پارامتر به آن میدهیم.

البته متد find موجود از تابعی که نوشتیم عمومی‌تری است که علاوه بر کاراکتر، تکه‌رشته هم میتواند در رشته ها پیدا کند:

>>> word.find(‘na’)

2

همینطور این متد میتواند پارامتر دومی هم بگیرد، اندیس جایی که باید شروع به جستجو کند:

>>> word.find(‘na’, 3)

4

و البته پارامتر سومی که اندیس جایی است که جستجو باید پایان یابد:

>>> name = ‘bob’

>>> name.find(‘b’, 1, 2)

-1

جستجو به نتیجه‌ای نمیرسد چون b بین بازه ۱ و ۲ وجود ندارد (خود ۲ در بازه وجود ندارد).

تمرین ۷

یک متد مربوط به رشته به اسم count وجود دارد که مشابه تابعی است که در موضوع قبل نوشتیم. مستندات مربوط به این متد را مطالعه کنید و یک فراخوان بنویسید که تعداد کاراکتر های a در رشته 'banana' را برگرداند.

تمرین ۸

مستندات مربوط به متد های رشته را در http://docs.python.org/2/library/stdtypes.html#string-methods مطالعه کنید. بهتر است هر کدام آنها را آزمایش کنید تا با کارایی آنها بیشتر آشنا شوید. strip و replace از پر استفاده ترین متد ها هستند.

این مستندات از نوعی Syntax استفاده میکنند که ممکن است کمی گیج‌کننده باشد. به عنوان مثال در find(sub[, start[, end]]) براکت ها نشان‌دهنده آرگومان‌های غیرضروری‌اند. در نتیجه sub ضروری است ولی start ضروری نیست. اگر از start استفاده کنید، آنگاه end هم قابل استفاده ولی غیرضروری است.

۸.۹ عملگر in

کلمه in یک عملگر بولین (Boolean) است که دو رشته را میگیرد و اگر رشته اول تکه رشته ای از دومی باشد مقدار True را برمیگرداند.

>>> 'a’ in ‘banana’

True

>>> ‘seed’ in ‘banana’

False

به عنوان مثال، تابع زیر تمام کاراکترهای word1 که در word2 هم وجود دارد را چاپ میکند.

def in_both(word1, word2)

for letter in word1:

if letter in word2

print letter

اگر نام متغیر ها به خوبی انتخاب شده باشند، پایتون تقریبا شبیه انگلیسی قابل خواندن خواهد بود. این حلقه را میتوان به این شکل خواند:

"For (each) letter in (the first) word, if (the) letter (appears) in (the second) word, print (the) letter.”

اگر دو رشته apples و oranges را با هم مقایسه کنید این نتیجه را میگیرید:

>>> in_both(‘apples’, ‘oranges’)

a

e

s

8.10 مقایسه رشته ها

عملگر های مقایسه ای روی رشته ها هم کار میکنند. برای اینکه ببینیم دو رشته برابرند:

if word == ‘banana’:

print ‘All right, bananas.’

بقیه عملگر های مقایسه ای مناسب مرتب کردن کمات در ترتیب الفبایی است:

if word < ‘banana’:

print ‘Your word,’ + word + ‘, comes before banana.’

elif word > ‘banana’:

print ‘Your word,’ + word + ‘, comes after banaba.’

else:

print ‘All right, bananas.’

پایتون حروف بزرگ و کوچک را مانند انسان ها نمیشناسد. تمام حروف بزرگ قبل از تمام حروف کوچک می آیند. در نتیجه:

Your word, Pineapple, comes before banana.

یک راه معمول برای رفع این مشکل این است که رشته را به حالتی استاندارد تبدیل کنیم. مثلا تماما حروف کوچک، و پس از آن مقایسه را انجام دهیم.

8.11 رفع مشکل (Debugging)

زمانی که میخواهید از اندیس ها استفاده کنید تا در رشته ها گردش کنید، ممکن است مشکل باشد که به اول یا آخر رشته به درستی گردش انجام شود. تابع زیر قرار است که دو کلمه را با هم مقایسه کند و مقدار True را برگرداند اگر یکی از آن دو کلمه برععکس دیگری باشد، ولی این کد حاوی دو خطاست:

def is_reverse(word1, word2):

if len(word1) != len(word2):

return False

i = 0

j = len(word2)

while j > 0:

if word1[i] != word2[j]

return False

i = i+1

j = j-1

اولین if بررسی میکن که طول دو رشته با هم برابر است و اگر نبود مقدار False را بلافاصله برمیگرداند، در باقی تابع، ما فرض میکنیم که طول دو رشته برابر است. این مثالی از پترن Guardian معرفی شده در بخش 6.8 است.

i و j اندیس هستند: i در word1 به سمت جلو گردش میکند در حالی که j در word2 به سمت عقب. اگر دو حرف پیدا کنیم که با هم برابر نیستند False را برمیگردانیم. اگر در کل حلقه بچرخیم و همه حروف با هم برابر باشند، مقدار True بازگردانده میشود.

اگر ما این تابع را با دو کلمه "pots" و "stop" امتحان کنیم، انتظار داریم که پاسخ True باشد ولی یک خطای IndexError دریافت میکنیم:

>>> is_reverse('pots', 'stop')\ ...\ File "reverse.py", line 15, in is_reverse\ if word1[i] != word2[j]:\ IndexError: string index out of range

برای رفع مشکل اینگونه خطا، اولین کاری که من انجام میدهم این است که همه اندیس ها را چاپ کنم تا جایی که به خطا میرسد.

while j > 0:\ print i, j # print here\ \ if word1[i] != word2[j]:\ return False\ i = i+1\ j = j-1

حالا اگر دوباره برنامه را اجرا کنیم، این اطلاعات را دریافت میکنیم:

>>> is_reverse(‘pots’, ‘stop’)

0 4

IndexError string index out of range

اولین بار که حلقه اجرا میشود، مقدار j برابر 4 است که خارج از بازه رشته 'pots' هست. اندیس آخرین کاراکتر 3 است، در نتیجه اولین مقدار j باید len(word2)-1 باشد.

اگر این خطا را اصلاح کنیم و برنامه را دوباره اجرا کنیم، این نتیجه را دریافت میکنیم:

>>> is_reverse('pots', 'stop')\ 0 3\ 1 2\ 2 1\ True

این بار ما پاسخ درست را دریافت میکنیم ولی به نظر میرسد که حلقه سه بار اجرا شده است که کمی مشکوک است. برای اینکه بهتر متوجه شویم که چه اتفاقی می افتد بهتر است که نمودار حالت مربوط به برنامه را بکشیم. حین اولین گردش، نموار is_reverse به شکل 8.2 است.

با رسم خط ها و مرتب کردن متغیر ها در این شکل مشخص شده که i و j اندیس های کاراکتر های کلمات word1 و word2 هستند.

تمرین 9

با استفاده از این نمودار برنامه را روی کاغذ اجرا کنید، مقدار های i و j را حین هر گردش عوض میکنیم. خطای دوم را پیدا کنید و مشکل را رفع کنید.

8.12 کلمات

object (شی):

چیزی که متغیر به آن اشاره میکند. فعلا، object (شی) را همان value (مقدار) فرض کنید.

sequence (رشته):

یک لیست مرتب شده؛ یعنی لیستی از مقادیر که هرکدام از مقدارها با یک اندیس از جنس عدد صحیح مشخص شده است.

item (مورد):

یکی از مقادیر موجود در sequence.

index (اندیس):

یه مقدار عدد صحیح که برای انتخاب یک item از sequence استفاده میشود، مانند انتخاب کاراکتر از رشته.

slice (تکه):

بخشی از رشته که با بازه‌ای از اندیس‌ها نمایش داده میشود.

empty string (رشته خالی):

یک رشته بدون کاراکتر با طول صفر، نمایش داده شده با علامت نقل قول.

immutable (غیرقابل تغییر):

عضوی از sequence که اعضای آن قابل تغییر نیست.

traverse (گردش):

چرخیدن بین آیتم‌های یک sequence و اعمال تغییراتی روی هرکدام.

search (جستجو):

شیوه‌ای از گردش که هر وقت چیزی که میخواهد را پیدا کرد، متوقف میشود.

counter (شمارنده):

متغیری که برای شمردن چیزی استفاده میشود، معمولا با مقدار صفر ساخته میشود و به آن اضافه میشود.

method (متد):

تابعی که به یک شی متصل شده و با علامت نقطه فراخوانی میشود.

invocation (فراخوانی):

عملگری که یک متد را فراخوانی میکند.

۸.۱۳ تمرین ها

تمرین ۱۰

یک تکه رشته (slice) میتواند یک اندیس سوم هم بگیرد که نشان دهنده طول قدم (step size)، به معنی فاصله بین هر دو کاراکتر از رشته که در برش قرار میگیرد. طول قدم ۲ به معنی یک کاراکتر در میان و طول قدم ۳ به معنی دو کاراکتر در میان است.

>>> fruit = ‘banana’

>>> fruit[0:5:2]

‘bnn’

طول قدم -۱ رشته را برعکس پیمایش میکند، در نتیجه برش مربوط به [-1::] یک رشته برعکس از رشته فعلی تولید میکند.

از این قابلیت استفاده کنید تا یک نسخه تک خطی از تابع is_palindrome از تمرین ۶ را بازنویسی کنید.

تمرین ۱۱

قرار است که تابع زیر بررسی کند که یک رشته شامل حروف کوچک میشود یا خیر، ولی شامل خطاست.

برای هر تابع توضیح دهید که هر تابع دقیقا چکار میکند (با توجه به اینکه پارامتر یک رشته است)/

def any_lowercase1(s):\ for c in s:\ if c.islower():\ return True\ else:\ return False\ \ def any_lowercase2(s):\ for c in s:\ if 'c'.islower():\ return 'True'\ else:\ return 'False'\ \ def any_lowercase3(s):\ for c in s:\ flag = c.islower()\ return flag\ \ def any_lowercase4(s):\ flag = False\ for c in s:\ flag = flag or c.islower()\ return flag\ \ def any_lowercase5(s):\ for c in s:\ if not c.islower():\ return False\ return True

تمرین ۱۲

ROT13 یک شیوه رمزگذاری بسیار ضعیف است که شامل "چرخاندن" هر حرف در کلمه تا ۱۳ تا جلوتر است؛ به این معنی که هر کاراکتر در الفبا به کاراکتری تبدیل میشود که ۱۳ تا از خودش جلوتر است. درنتیجه چرخاندن A به ۳ تا جلوتر به D تبدیل میشود و چرخاندن Z به یکی جلوتر به A.

تابعی با نام rotate_word بنویسید که یک رشته و یک عدد صحیح به عنوان پارامتر میگیرد، رشته تازه‌ای برمیگرداند که حروف در آن نسبت به حروف رشته اصلی به مقدار عدد صحیح وارد شده چرخیده‌اند.

به طور مثال cheer اگر به مقدار ۷ بچرخد به jolly تبدیل میشود و melon اگر به مقدار -۱۰ بچرخد به cubed.

شاید بخواهید از توابع از پیش ساخته ord که هر حرف را به یک کد عددی و chr که آن کد ها را به حروف تبدیل میکند استفاده کنید.

برخی از جوک‌های اهانت آمیر در اینترنت به شیوه ROT13 رمزگذاری شده‌اند. اگر فکر میکنید که به شما برنمیخورد، آن‌ها را پیدا کنید و رمزشان را بشکنید. راه‌حل: ‌http://thinkpython.com/code/rotate.py